<?php
ArrowController::importModel("controls.ArrowCtrlStateManager");
ArrowController::importModel("controls.IArrowControlsManager");
abstract class ArrowNode{
	
	private $parent = false;
	private $requiredParent = false;
	private $prefix;
	private $name;
	private $args;
	private $globalId;
	private $id;
	private $children = array();
	private $disabled = false;
	private $templateId = null;
	private $actions = array();
	
	
	private static $registredEvents = array();	
	
	const PARSE_PROPERTIES = 0;
	const DO_NOT_PARSE_PROPERTIES = 1;
	private static $porpetyParseMode = self::PARSE_PROPERTIES;
	
	const APPEND = 0 ;
	const PREPEND = 1 ;
	
	const CTRL_NAMESPACE = "ctrl";
	
	/**
	 * Required control properties ( attributes )
	 *
	 * @var array
	 */
	private $requiredProperties = array();
	
	/**
	 * Default control properties ( attributes )
	 *
	 * @var array
	 */	
	private $properties = array(
		"ctrlId" => null,
		"contextId" => null
	);
	
	public $stateProperties = array(
		"contextId" 
	);
	
	/**
	 * Non configuration control properties like
	 * style tag. Used for generating html
	 *
	 * @var array
	 */	
	private $externalProperties = array();
	
	private $nodeIndex;
	public static $index = 0;
	private $nodeClass;
	private $display = true;
	private $lazyInit = false;
	
	private $smartyValues = array();
	
	private $errorOnUnknowAttribute = true;

	public function __construct( $args, $prefix, $name, $templateId, $class = "ArrowNode", $smartyArgs = array() ){
		$this->prefix = $prefix;
		$this->name = $name;
		
		$this->args = $args;
		$this->smartyValues = $smartyArgs;
		$this->nodeIndex = self::$index;
		self::$index++;
		$this->nodeClass = $class;
		$this->templateId = $templateId;
	}
	
	public function configure(){}
	
	public function setup($nodeIndex = false){
		//FB::log(get_class($this));
		if($nodeIndex)
			$this->nodeIndex = $nodeIndex;
		foreach( $this->args as $property => $value ){
			if( key_exists($property, $this->properties) ){ 
				$this->properties[$property] = $value;
			}elseif( $this->throwErrorOnUnknowAttribute() ){
				throw new ArrowCoreException( array(
					"msg" => "Unknow attribute in tag ".$this, 
					"attribute" => $property,
					"validProperties" => $this->properties
				));
			}else{
				if(self::isSmartyValue($value))
					$this->smartyValues[] = $property;
				$this->externalProperties[$property] = $value;
			}
		}
		
		foreach( $this->requiredProperties as $property ){
			if( !isset( $this->properties[$property] ) || $this->properties[$property] === null )
				throw new ArrowCoreException( array(
					"msg" => "Missing attribute '$property' in tag '$this'" 
				) );
		}
		
		if( $this->requiredParent != false && $this->getParent($this->requiredParent) == null ){
			throw new ArrowCoreException( array(
				"msg" => "Missing required parent for tag ".$this, 
				"required parent" => $this->requiredParent 
			));
			
		}
		foreach ($this->stateProperties as $name){
			$state = $this->getState($name);
			if( $state !== null ){
				$this->setProperty($name, $state);
			}
		}
		
		foreach($this->getChildren("ArrowCtrlAbstractEvent") as $event){
			FB::log("event detected");
			self::$registredEvents[$this->getEventClass()][] = array( 
				"on" => $event->getProperty("on"),
				"type" => $event->getProperty("type")
			);
		}
		
	}
	
	protected function getStateChangeLink( $parameters , $forceUrl = false, $encode = true){
		$state = ArrowCtrlStateManager::getDefault();
		return $state->getStateChangeLink( $this, $parameters, $forceUrl, $encode );
	}
	protected function getState( $stateName ){
		$state = ArrowCtrlStateManager::getDefault();
		return $state->getState( $this, $stateName );
	}
	
	/**
	 * Returns control Id
	 *
	 * @return string
	 */
	public function getId(){
		if(!$this->id){
			if($this->getProperty("ctrlId"))
				$this->id = $this->getProperty("ctrlId");
			else
				$this->id = $this->nodeIndex."".get_class($this);
		}
		return $this->id;
	}	
	
	/**
	 * Returns control global Id
	 *
	 * @return string
	 */
	public function getGlobalId(){
		if(!$this->globalId){
			$templateId = ArrowView::getCurrentView()->getTemplateDescriptor()->getKey();
			
			if($this->getProperty("ctrlId"))
				$this->globalId = $this->getProperty("ctrlId").$templateId;
			else
				$this->globalId = $this->nodeIndex."".get_class($this).$templateId;
				
		}
		
		return $this->globalId;
	}
	
	public function getTemplateId(){
		return $this->templateId;
	}
	
	public function getTemplateHost(){
		return ArrowTemplatesStructure::getDefault(ArrowController::$activeProject)->getTemplateById($this->getTemplateId())->getPageClass();
	}
	
	public function getPropertyAsModel( $name ){
		$obj = $this->getProperty($name);
		if( is_object($obj) ){
			return $obj;
		}else{
			ArrowController::importModel( $obj );
			$arr = explode( ".", $obj );
			$className = array_pop($arr);
			return $className;
		}
	}
	
	public function init(){}
	
	
	protected function addRequiredProperties($propertyArr){
		$this->requiredProperties = array_merge( $this->requiredProperties, $propertyArr );
	}
	protected function addProperties( $propertyArr ){
		$this->properties = array_merge( $this->properties, $propertyArr );
	}
	
	protected function addStateProperties( $propertyArr ){
		$this->stateProperties = array_merge( $this->stateProperties, $propertyArr );
	}
	
	public function setProperty( $name, $value ){
		if( key_exists($name, $this->properties) ){
			$this->properties[$name] = $value;
		if( in_array( $name, $this->stateProperties) )
			ArrowCtrlStateManager::getDefault()->setState($this, $name, $value);
		}else{
			throw new ArrowException( array(
				"msg" => "Unknow property in tag ".$this, 
				"property" => $name 
			));
		}
	}
	
	public static function setPropertiesParseMode($mode){
		self::$porpetyParseMode = $mode;
	}
	//public static $counter = array();
	public function getProperty( $name ){
		/*
		if( isset(self::$counter[get_class($this).": ".$name]) )
			self::$counter[get_class($this).": ".$name] ++;
		else 
			self::$counter[get_class($this).": ".$name] = 1;
		FB::log($name, get_class($this));
		*/
		
		if( is_string($name) && key_exists($name, $this->properties) ){
			if(in_array($name, $this->smartyValues ))
				return self::getRealVarValue($this->properties[$name]);
			return $this->properties[$name];
		}
		return false;
	}
	
	private static function getRealVarValue( $value ){
		if(empty($value) || !is_string($value))
			return $value;
		if(self::isPhpValue( $value ) && isset($$value)){
			return $$value;
		}elseif(self::isSmartyValue( $value )){
			$val = self::smartyValue($value);
			if($val == null && $value == "context" && $this->getProperty("context")){
				$val = $this->getContext($this->getProperty("context"));
			}
			return $val;			
		}else{
			return $value; 
		}		
		return $value;
	}
	
	protected static function isPhpValue($valueString){
		if( $valueString[0] == '$' )
			return true;
		return false;
	}

	protected static function isSmartyValue($valueString){
		if( !is_string($valueString))
			return false;
		if( substr($valueString,0,2) == '{$' )
			return true;
		return false;
	}	
	
	private static function smartyValue( $value ){
		$tmpArr = explode( ".", str_replace( array( '{$', '}' ), "", $value  ));
		$ts = ArrowView::getCurrentView()->getTemplateSystem();
		if(!isset($ts->_tpl_vars[$tmpArr[0]]) ){
			return null;
		}
		$val = $ts->_tpl_vars[$tmpArr[0]];
		$count = count($tmpArr);
		for($i=1;$i<$count; $i++){
			$val = $val[$tmpArr[$i]];
		}
		return $val;
	}
	
	private static $registredJs = array();
	protected function registerJs($class, $file, $selector, $data = array()){
		if(array_key_exists ($class, self::$registredJs)){
			$allData = array( "value" => $selector, "data" => $data );
			if(!in_array($allData, self::$registredJs[$class]["selector"]))
				self::$registredJs[$class]["selector"][] =  $allData;
		}else
			self::$registredJs[$class] = array("file" => $file, "selector" => array( array("value" => $selector, "data" => $data )));
	}
	
	protected function clearRegistredJs(){
		self::$registredJs = array();
	}
	
	protected function getRegistredJs(){
		return self::$registredJs;
	}
	
	
	protected function getRegistredEvents(){
		return self::$registredEvents;
	}
	
	private static $registredCssFiles = array();
	protected function registerCssFile($file){
		if(!in_array($file, self::$registredCssFiles)) {
			//if( !file_exists($file) ) throw new ArrowProjectsException( "File doesn't exist $file" ) ;
			assert( file_exists($file) );	
			self::$registredCssFiles[] =  $file;
		}
	}
	
	protected function getRegistredCssFiles(){
		return self::$registredCssFiles;
	}	
	
	private static $registredJsFiles = array();
	protected function registerJsFile($file){
		if(!in_array($file, self::$registredJsFiles)) {
			//if( !file_exists($file) ) throw new ArrowProjectsException( "File doesn't exist $file" ) ;
			assert( function() { return file_exists($file); } );
			self::$registredJsFiles[] =  $file;
		}
	}
	
	protected function getRegistredJsFiles(){
		return self::$registredJsFiles;
	}
	
	protected function clearRegistredJsFiles(){
		self::$registredJsFiles = array();
	}
	
	
	
	public function getProperties(){
		return $this->properties;
	}
	
	public function getRequiredProperties(){
		return $this->requiredProperties;
	}
	
	public function getRequiredParent(){
		return $this->requiredParent;
	}
	public function setRequiredParent( $class ){
		$this->requiredParent = $class;
	}
	
	public function getExternalProperty( $name ){
		if( key_exists($name, $this->externalProperties) )
			return $this->externalProperties[$name];
		return false;
	}	
	
	public function setExternalProperty( $name, $value ){
		$this->externalProperties[ $name ] = $value ;
	}
	
	public function getExternalProperties(){
		return $this->externalProperties;
	}
	
	public function addExternalProperties( $properties ){
		$this->externalProperties = array_merge( $this->externalProperties, $properties );
	}
	
	public function setErrorOnUnknowAttribute( $setting ){
		$this->errorOnUnknowAttribute = $setting;
	}
	
	public function throwErrorOnUnknowAttribute(){
		return $this->errorOnUnknowAttribute;
	}
	
	public function __toString(){
		return $this->prefix.":".$this->name. " [".get_class($this)."]";
	}
	
	public function isDisabled(){
		if($this->getParent() != null && $this->getParent()->isDisabled() == true)
			return true;
		return $this->disabled;
	}
	
	public function setDisabled($mode){
		
		$this->disabled = $mode;
	}
	
	public function setDisplay( $display ){
		$this->display = $display;
	}
	
	public function isDisplayed(){
		return $this->display;
	}	
	
	public function setLazyInit( $setting ){
		$this->lazyInit = $setting;
	}
	
	public function isLazyInitiated(){
		if($this->getParent() != null && $this->getParent()->isLazyInitiated() == true)
			return true;
		return $this->lazyInit;
	}
	
	public function setContext($id, $context){
		$_SESSION[self::CTRL_NAMESPACE]["contexts"][$id] = $context;
	}
	
	public function getContext($id){
		return $_SESSION[self::CTRL_NAMESPACE]["contexts"][$id];
	}

	public function setParent( $parent = null, $place = self::APPEND ){
		$currentParent = $this->getParent();
		
		
		if($currentParent != null) $currentParent->removeChild($this); 
		
		
		$this->parent = $parent;
		if($parent != null) { 
			if( $place == self::APPEND ) $this->parent->addChild($this);
			else $this->parent->prependChild($this);
		}
	}
	
	public function addChild( ArrowNode $child ){
		$this->children[] = $child;
	}
	
	public function removeChild($toRemove){
		foreach($this->children as $key => $child){
			if( $child->getNodeIndex() == $toRemove->getNodeIndex()  ){
				unset($this->children[$key]);
			}
		}
	}
	
	public function prependChild( ArrowNode $child ){
		array_unshift( $this->children, $child ) ;
	}
	
	public function initAll(){
		foreach($this->getAllChildren() as $child){
			$child->init();
		}
		$this->init();
	}
	
	public function getFirstChild( $class = false ){
		$children = $this->getChildren($class);
		if(empty($children))
			return null;
			
		return $children[0];
	}
	public function getChildren($class = false){
		$children = $this->children;
		if($class){
			$tmp = array();
			foreach( $children as $obj ){
				if( $obj instanceof $class )
					$tmp[] = $obj;
			}
			return $tmp;
		}
	
		return $children;
	}
	
	public function removeChildren( $class ){
		$children = $this->children;
		$tmp = array();
		foreach( $children as $key => $obj ){
			if( $obj instanceof $class ){
				$obj->setParent(null);
				unset($this->children[$key]);
			}
		}
	}

	
	public function findChildren( $class, $property, $propertyVal ){
		$tmp = array();
		$children = $this->getChildren($class);
		foreach($children as $child)
			if( $child->getProperty($property) == $propertyVal )
				$tmp[] = $child;
		return $tmp;
	}
	
	/**
	 * Returns node by id
	 * @param String $ctrlId
	 * @return ArrowNode
	 */
	public function getById( $ctrlId ){
		$tmp = array();
		foreach( $this->getAllChildren() as $child ){
			if($ctrlId == $child->getId())
				return $child;
		}
		return null;
	}
	
	/**
	 * Returns all children of node
	 * @param Class filter $class
	 * @return ArrowNode[]
	 */
	public function getAllChildren($class = false){
		$children = array(); 
		foreach( $this->children as $child ){
			$children[] = $child;
			if($child instanceof ArrowTagContainer)
				$children = array_merge( $children, $child->getAllChildren() );
		}
		
		if($class){
			$tmp = array();
			foreach( $children as $obj ){
				if( $obj instanceof $class )
					$tmp[] = $obj;
			}
			return $tmp;
		}
		return $children;
	}	

	/**
	 * Returns direct parent ( if no class suplied )
	 * or parent of specyfied class  
	 * @param $class
	 * @return ArrowTagContainer
	 */
	public function getParent($class=false){
		if( $class != false ){
			$el = $this;
			while(  $parent = $el->getParent() ){
				if($parent instanceof $class)
					return $parent;
				$el = $parent;
			}
			return null;
		}
		return $this->parent;
	}
	
	public function getArg($argumentName){
		if(!isset($this->args[$argumentName]))
			throw new Exception("No such argument");
		return $this->args[$argumentName];
	}
	public function getArgs(){
		return $this->args;
	}
	
	public function setArg( $name, $value ){
		return $this->args[$name] = $value;
	}
	
	public function getNodeIndex(){
		return $this->nodeIndex;
	}
	
	public function getSetupInstructions(){
		$str = "";
		$parent = $this->getParent();
		if($parent){
			$args = $this->getArgs();
			$smartyArgs = array();
			foreach($args as $key => $arg)
				if( self::isSmartyValue($arg))
					$smartyArgs[] = $key;
			
			$str.= '$arrowTag'.$this->nodeIndex.' = new '.$this->nodeClass."( array(". ArrowCacheProvider::array2string( $args ) ."), '{$this->prefix}','{$this->name}', \$templateId, '{$this->nodeClass}', array(". ArrowCacheProvider::array2string( $smartyArgs ) ."));\n";
			$str.= '$arrowTag'.$this->nodeIndex.'->setParent( $arrowTag'.$parent->nodeIndex.' );'."\n";
			$str.= '$arrowTag'.$this->nodeIndex.'->configure();'."\n";
			$str.= '$arrowTag'.$this->nodeIndex.'->setup('.$this->nodeIndex.');'."\n";
			$str.= '$ts->assign( "arrowTag'.$this->nodeIndex.'", $arrowTag'.$this->nodeIndex.' );'."\n";	
		}else{
			$str = '$arrowTag0 = new ArrowTagContainer(array(),"root","root", $templateId);'."\n";
			$str.= '$rootTag = $arrowTag0'.";\n";
			$str.= 'ArrowView::getCurrentView()->setControlsRoot($rootTag);'."\n";
		}
		return $str;
		
	}
	
	public function getInitInstructions(){
		if($this->isLazyInitiated())
			$str= "//lazy load";
		else
			$str= '$arrowTag'.$this->getNodeIndex().'->init();'."\n";
		return $str;		
	}
	
	public function getPreInitInstructions(){
		return "";
	}
	
	protected function addActionDelegate($name, $method = false){
		if($method == false) $method = $name;
		$this->actions[$name] = $method;
	}
	
	public function runActionDelegate($name, $args){
		if(!array_key_exists($name, $this->actions))
			throw new ArrowException(array("msg" => "Action $name not defined for {$this->getId()} , ".get_class($this)));
		return call_user_func( array($this, $this->actions[$name]) , $args );
	}
	
	
	protected function getEventClass(){
		return "ctrl-event-".$this->getId();
	}
	
	protected function runEvent($eventName, $args){
		if(isset( $this->events[$eventName] )){
			foreach($this->events[$eventName] as $event)
				FB::log("dupa");
		}
	}
	
	/**
	 * Adding new instantion of child control to object
	 * @param string $prefix
	 * @param string $name
	 * @param array $attributes
	 * @return ArrowNode
	 */
	public function addNode($prefix, $name, $attributes, $init = true ){
		$parserConf = ArrowConfProvider::getDefault()->getConf(ArrowConfProvider::TEMPLATE_PARSER);
		$tmpInstances = array();
				
		foreach( $parserConf as $parser ){
			if($parser["tag"][0] == $prefix && ($parser["tag"][1] == $name || $parser["tag"][1] == "*") ){
				ArrowController::importModel($parser["model"]);
				
				if( $parser["method"] ){
					$instance = ArrowController::getModelInstance($parser["model"]);
					
 					$obj = call_user_func_array( 
						array($instance, $parser["method"]),
						array( $attributes, $prefix, $name, ""  )
					);
					$obj = $obj["object"]; 

					
				}else{
					$ex = explode( ".", $parser["model"] );
					$class = array_pop($ex);
					$obj = new $class( $attributes, $prefix, $name, ArrowView::getCurrentView()->getTemplateDescriptor()->getKey(), $class );
				}
				
				$obj->configure();
				$obj->setParent($this);
				$obj->setup();
				if($init) $obj->init();
				//ArrowView::getCurrentView()->ts->assign("arrowTag".$obj->getNodeIndex(), $obj);
				return $obj;
			}
		}
		throw new ArrowCoreException(array("msg" => "Node definition not found '$prefix:$name'" ));
	}
	
	public function prependNode($prefix, $name, $attributes, $init = true ){
		$parserConf = ArrowConfProvider::getDefault()->getConf(ArrowConfProvider::TEMPLATE_PARSER);
		$tmpInstances = array();
				
		foreach( $parserConf as $parser ){
			if($parser["tag"][0] == $prefix && ($parser["tag"][1] == $name || $parser["tag"][1] == "*") ){
				ArrowController::importModel($parser["model"]);
				
				if( $parser["method"] ){
					$instance = ArrowController::getModelInstance($parser["model"]);
 					$obj = call_user_func_array( 
						array($instance, $parser["method"]),
						array( $attributes, $prefix, $name, ""  )
					);
					$obj = $obj["object"]; 

					
				}else{
					$ex = explode( ".", $parser["model"] );
					$class = array_pop($ex);
					 
					
					$obj = new $class( $attributes, $prefix, $name, $class );
				}
				
				$obj->configure();
				$obj->setParent($this, self::PREPEND);
				$obj->setup();
				if($init) $obj->init();
				return $obj;
			}
		}
		throw new ArrowCoreException(array("msg" => "Node definition not found '$prefix:$name'" ));
		
	}
	
	
	public function printDtd(){
		print_r( $this->properties );
		print_r($this->requiredProperties);
		print_r($this->requiredParent);
	}
	
	abstract public function generate();
	
}
?>